package org.judo.validation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.judo.propertyobject.PropertyObject;
import org.judo.util.JavaNameConversion;
import org.judo.components.uicontrol.validation.*;

public class FieldValidator
{
	List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
	SimpleDateFormat dateFormater = new SimpleDateFormat("MM/dd/yyyy");
	
	public FieldValidator(PropertyObject obj,HttpServletRequest req,String javaActionName) 
		throws ValidationSystemException
	{
		String __validate = req.getParameter("__validate"); 
		
		if (__validate == null || __validate.equals("") || !__validate.equals("true"))
			return;
		
		String ignoreValidation = (String)req.getAttribute("ignore_valdiation");
		
		if (ignoreValidation != null && ignoreValidation.equals("true"))
			return;
		
		Field fields[] = obj.getClass().getDeclaredFields();
		
		for (Field field:fields)
		{
			
			Object value = null;
			field.setAccessible(true);
			String fieldName = field.getName(); 
				
			try
			{
				value = field.get(obj);
			} 
			catch (Exception e){
				e.printStackTrace();
			}
			
			DisplayName displayName =
				field.getAnnotation(DisplayName.class);
			
			if (displayName != null)
			{
				fieldName = displayName.value();
			}
			
			ValidateOnAction actionValidation =
				field.getAnnotation(ValidateOnAction.class);
			
			if (actionValidation != null)
			{
				String name = actionValidation.value();
				
				if (!name.equals(javaActionName))
					continue;
			}
			
			Annotation annotations[] = field.getAnnotations();
			
			for (Annotation annotation:annotations)
			{
				if (annotation instanceof Required)
				{
					if (value == null || value.equals("") || value.toString().equals("0") || value.toString().equals("0.0") || value.toString().equals("NA"))
					{
						messages.add(new ValidationMessage(fieldName,
								fieldName+" is a required Value"));
					}
				}
				else if (annotation instanceof After)
				{		
					After after = (After)annotation;
					
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							
							try{
								String dateValue = obj.parse(after.value());
								Date testDate = dateFormater.parse(dateValue);
					
								if (eneteredDate.getTime() <= testDate.getTime())
									messages.add(new ValidationMessage(fieldName,"This date must be after: "+after.value()));
							} 
							catch (ParseException e){
								throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @After validation could not be processed: "+e);
							}
						}
						else
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @After validation can only be used on type java.util.Date");
					}
				}
				else if (annotation instanceof AfterOrEqualTo)
				{
					AfterOrEqualTo after = (AfterOrEqualTo)annotation;
					
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							
							try{
								String dateValue = obj.parse(after.value());
								Date testDate = dateFormater.parse(dateValue);
					
								if (eneteredDate.getTime() < testDate.getTime())
									messages.add(new ValidationMessage(fieldName,"This date must be before or equal to: "+after.value()));
							} 
							catch (ParseException e){
								throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @AfterOrEqualTo validation could not be processed: "+e);
							}
						}
						else
							throw new ValidationSystemException("Error on validating '"+field.getName()+"'. The @AfterOrEqualTo validation can only be used on type java.util.Date");
					}
				}
				else if (annotation instanceof AfterOrToday)
				{
					AfterOrToday after = (AfterOrToday)annotation;
					
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							Date testDate;
							
							testDate = new Date();
							
							if (eneteredDate.getTime() < testDate.getTime())
								messages.add(new ValidationMessage(fieldName,"This date must be after or equal to todays date"));			
						}
						else
							throw new ValidationSystemException("Error on validating '"+field.getName()+"'. The @AfterOrToday validation can only be used on type java.util.Date");
					}	
				}
				else if (annotation instanceof AfterToday)
				{	
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							Date testDate;
							
							testDate = new Date();
							
							if (eneteredDate.getTime() <= testDate.getTime())
								messages.add(new ValidationMessage(fieldName,"This date must be after todays date"));			
						}
						else
							throw new ValidationSystemException("Error on validating '"+field.getName()+"'. The @AfterToday validation can only be used on type java.util.Date");
					}		
				}
				else if (annotation instanceof EarlierOrEqualTo)
				{
					EarlierOrEqualTo earlier = (EarlierOrEqualTo)annotation;
					
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							
							try{
								String dateValue = obj.parse(earlier.value());
								Date testDate = dateFormater.parse(dateValue);
					
								if (eneteredDate.getTime() > testDate.getTime())
									messages.add(new ValidationMessage(fieldName,"This date must be ealier than or equal to: "+earlier.value()));
							} 
							catch (ParseException e){
								throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @EarlierOrEqualTo validation could not be processed: "+e);
							}
						}
						else
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @EarlierOrEqualTo validation can only be used on type java.util.Date");
					}
				}
				else if (annotation instanceof EarlierOrToday)
				{
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							Date testDate;
							
							testDate = new Date();
							
							if (eneteredDate.getTime() > testDate.getTime())
								messages.add(new ValidationMessage(fieldName,"This date must be before or equal to todays date"));			
						}
						else
							throw new ValidationSystemException("Error on validating '"+field.getName()+"'. The @EarlierOrToday validation can only be used on type java.util.Date");
					}
				}
				else if (annotation instanceof EarlierThan)
				{
					EarlierThan earlier = (EarlierThan)annotation;
					
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							
							try{
								String dateValue = obj.parse(earlier.value());
								Date testDate = dateFormater.parse(dateValue);
					
								if (eneteredDate.getTime() >= testDate.getTime())
									messages.add(new ValidationMessage(fieldName,"This date must be before: "+earlier.value()));
							} 
							catch (ParseException e){
								throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @EarlierThan validation could not be processed: "+e);
							}
						}
						else
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @EarlierThan validation can only be used on type java.util.Date");
					}	
				}
				else if (annotation instanceof EarlierThanToday)
				{
					if (value != null && !value.equals(""))
					{	
						if (value instanceof java.util.Date)
						{	
							Date eneteredDate = (Date)value;
							Date testDate;
							
							testDate = new Date();
							
							if (eneteredDate.getTime() >= testDate.getTime())
								messages.add(new ValidationMessage(fieldName,"This date must be before todays date"));			
						}
						else
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @EarlierThanToday validation can only be used on type java.util.Date");
					}
				}
				else if (annotation instanceof GreaterOrEqual)
				{
					if (value != null && !value.equals(""))
					{
						GreaterOrEqual aurAnnotation = (GreaterOrEqual)annotation;
						Object compareValue = aurAnnotation.value();
						
						try
						{
							Double anotationNum = Double.parseDouble(compareValue.toString());
							Double fieldNum = Double.parseDouble(value.toString());
							
							if (!(fieldNum >= anotationNum))
								messages.add(new ValidationMessage(fieldName,"Number can not be smaller than "+anotationNum));
						}
						catch(NumberFormatException e)
						{
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @GreaterOrEqual validation can only be used on numberic types");
						}
					}	
				}
				else if (annotation instanceof GreaterThan)
				{
					if (value != null && !value.equals(""))
					{
						GreaterThan aurAnnotation = (GreaterThan)annotation;
						Object compareValue = aurAnnotation.value();
						
						try
						{
							Double anotationNum = Double.parseDouble(compareValue.toString());
							Double fieldNum = Double.parseDouble(value.toString());
							
							if (!(fieldNum > anotationNum))
								messages.add(new ValidationMessage(fieldName,"Number can not be smaller than "+(anotationNum+1)));
						}
						catch(NumberFormatException e)
						{
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @GreaterThan validation can only be used on numberic types");
						}
					}
				}
				else if (annotation instanceof InList)
				{
					if (value == null || value.equals(""))
					{
						continue;
					}
					
					try
					{
						InList antn = (InList)annotation;
						Field specifiedField = obj.getClass().getDeclaredField(antn.value());
						specifiedField.setAccessible(true);
						Object fieldValue = specifiedField.get(obj);
						List list = (List)fieldValue;
						
						if (!list.contains(value))
							messages.add(new ValidationMessage(fieldName,"The value '"+value+"' is not a permissable value"));
					} 
					catch (Exception e){
						throw new ValidationSystemException("Error on validating '"+fieldName+"': "+e);
					}
				}
				else if (annotation instanceof LessOrEqualTo)
				{
					if (value != null && !value.equals(""))
					{
						LessOrEqualTo curAnnotation = (LessOrEqualTo)annotation;
						Object compareValue = curAnnotation.value();
						
						try
						{
							Double anotationNum = Double.parseDouble(compareValue.toString());
							Double fieldNum = Double.parseDouble(value.toString());
							
							if (!(fieldNum <= anotationNum))
								messages.add(new ValidationMessage(fieldName,"Number must be smaller or equal to "+(anotationNum)));
						}
						catch(NumberFormatException e)
						{
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @LessOrEqualTo validation can only be used on numberic types");
						}
					}
				}
				else if (annotation instanceof LessThan)
				{
					if (value != null && !value.equals(""))
					{
						LessThan curAnnotation = (LessThan)annotation;
						Object compareValue = curAnnotation.value();
						
						try
						{
							Double anotationNum = Double.parseDouble(compareValue.toString());
							Double fieldNum = Double.parseDouble(value.toString());
							
							if (!(fieldNum < anotationNum))
								messages.add(new ValidationMessage(fieldName,"Number must be smaller than "+(anotationNum)));
						}
						catch(NumberFormatException e)
						{
							throw new ValidationSystemException("Error on validating '"+fieldName+"'. The @LessThan validation can only be used on numberic types");
						}
					}	
				}
				else if (annotation instanceof MaxLength)
				{
					if (value == null)
					{
						continue;
					}
					
					MaxLength curAnnotation = (MaxLength)annotation;
					int length = curAnnotation.value();
					String strValue = value.toString();
					
					if (strValue.length() > length)
						messages.add(new ValidationMessage(fieldName,"Value must be less than "+length+" characters long"));
				}
				else if (annotation instanceof MinLength)
				{
					if (value == null)
					{
						continue;
					}
					
					MinLength curAnnotation = (MinLength)annotation;
					int length = curAnnotation.value();
					String strValue = value.toString();
					
					if (strValue.length() < length)
						messages.add(new ValidationMessage(fieldName,"Value must be greater than "+length+" characters long"));
				}
				else if (annotation instanceof OneOf)
				{
					if (value == null || value.equals(""))
					{
						continue;
					}
					
					OneOf curAnnotation = (OneOf)annotation;
					String values = curAnnotation.value();
					String valueList[] = values.split(",");
					String strValue = value.toString();
					
					boolean match = false;
					
					for (String cur:valueList)
					{
						if (cur.equals(strValue))
						{
							match = true;
							break;
						}
					}
					
					if (!match)
						messages.add(new ValidationMessage(fieldName,"Not a valid value"));
				}
				else if (annotation instanceof RegExpr)
				{
					if (value == null || value.equals(""))
					{
						continue;
					}
					
					RegExpr curAnnotation = (RegExpr)annotation;
					
					if (!Pattern.matches(curAnnotation.value(),value.toString()))
						messages.add(new ValidationMessage(fieldName,"Not a valid value"));
				}
			}
		}
	}
	
	public boolean validationFailed()
	{
		if (messages.size() == 0)
			return false;
		
		return true;
	}
	
	public List<ValidationMessage> getValidationMessages()
	{
		return messages;
	}
}
